﻿///Author: Alexander Persson
///License: MS-PL

using ap.DatabaseUtil.Helpers;
using ap.DatabaseUtil.Models;
using System;
using System.Collections.Generic;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Reflection;
using System.Linq;

namespace ap.DatabaseUtil
{
    public class Checker
    {
        private List<string> Errors;
        private IEnumerable<Limit> Limits;
        private ObjectContext Context;
        private List<Object> Tested;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="context">The ObjectContext to use when checking for errors</param>
        /// <param name="databaseName">The database</param>
        /// <param name="databaseSchema">The databaseschema</param>
        public Checker(ObjectContext context, string databaseName, string databaseSchema = "dbo")
        {
            Context = context;
            Tested = new List<object>();
            Limits = DbHelper.GetLimits(Context, databaseName, databaseSchema);
            Errors = new List<string>();
        }

        /// <summary>
        /// Checks the object for errors
        /// </summary>
        /// <param name="toTest">The object to check</param>
        /// <returns>A list of errors</returns>
        public IEnumerable<string> GetErrors(object toTest)
        {
            var propertyInfo = toTest.GetType().GetProperties();
            Tested.Add(toTest);

            foreach (var item in propertyInfo)
            {
                Check(item, toTest);
            }

            return Errors;
        }

        private void Check(PropertyInfo property, object toTest)
        {
            if (property.PropertyType == typeof(string))
            {
                CheckString(property, toTest);
            }
            else if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(EntityCollection<>))
            {
                CheckList(property, toTest);   
            }
            else if (property.PropertyType.IsSubclassOf(typeof(EntityObject)))
            {
                CheckObject(property, toTest);
            }
        }

        private void CheckString(PropertyInfo property, object toTest)
        {
            var table = Context.GetTableName(toTest);
            
            if (Limits.Any(e => e.Table == table && e.ColumnName == property.Name))
            {
                var limit = Limits.SingleOrDefault(e => e.Table == table && e.ColumnName == property.Name);
                var test = ((string)property.GetValue(toTest, null));
                if (string.IsNullOrWhiteSpace(test))
                    return;
                var length = test.Length;
                if (length > limit.Length)
                {
                    Errors.Add(string.Format("{0}.{1}, Lenght: {2}, Max allowed: {3}", limit.Table, property.Name, length, limit.Length));
                }
            }
        }

        private void CheckList(PropertyInfo property, object toTest)
        {
            var type = property.PropertyType.GetGenericArguments()[0];
            var pi = type.GetProperties();

            var collection = property.GetValue(toTest, null);

            foreach (var colItem in collection as IEnumerable<object>)
            {
                if (Tested.Contains(colItem))
                    continue;
                else
                    Tested.Add(colItem);

                foreach (var piItem in pi)
                {
                    Check(piItem, colItem);
                }
            }
        }

        private void CheckObject(PropertyInfo property, object toTest)
        {
            var obj = property.GetValue(toTest, null);
            if (obj == null)
                return;
            var propertyInfo = obj.GetType().GetProperties();

            if (Tested.Contains(obj))
                return;
            else
                Tested.Add(obj);

            foreach (var item in propertyInfo)
            {
                Check(item, obj);
            }
        }
    }
}